package com.my.lucky.service;

import com.my.lucky.model.Stock;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Selector {
    /**
     * 根据选择器类型选择对应的选股策略
     *
     * @param picker   选择器类型：momentum(动量)、value(价值)、growth(成长)、quality(质量)
     * @param stockMap 股票数据Map，key为股票代码，value为该股票的历史数据
     * @param topN     选择的股票数量
     * @return 选中的股票代码列表
     */
    public static List<String> select(String picker, Map<String, List<Stock>> stockMap, int topN) {
        switch (picker) {
            case "momentum":
                return selectByMomentum(stockMap, topN);
            case "value":
                return selectByValue(stockMap, topN);
            case "growth":
                return selectByGrowth(stockMap, topN);
            case "quality":
                return selectKDJGoldenCross(stockMap, 10);
            case "multiFactor":
                return selectMultiFactor(stockMap, 15, topN);
            case "oneToTwo":
                return selectOneToTwo(stockMap, 20, 15);
            case "leader":
                return selectLeaderStocks(stockMap, 15, 10);
            default:
                //  return selectMultiFactor(stockMap, 15, topN);
                return selectMultiFactor(stockMap, 15, topN);
        }
    }

    /**
     * 基于动量策略选股
     * 计算5日涨幅，选择涨幅最大的topN只股票
     *
     * @param stockMap 股票数据Map
     * @param topN     选择的股票数量
     * @return 选中的股票代码列表
     */
    public static List<String> selectByMomentum(Map<String, List<Stock>> stockMap, int topN) {
        List<Map.Entry<String, Double>> momentumList = new ArrayList<>();
        for (Map.Entry<String, List<Stock>> entry : stockMap.entrySet()) {
            List<Stock> stocks = entry.getValue();
            if (stocks.size() < 5)
                continue;                        // 计算5日涨幅
            Stock startStock = stocks.get(stocks.size() - 5);
            Stock endStock = stocks.get(stocks.size() - 1);
            double momentum = (endStock.getClose() - startStock.getClose()) / startStock.getClose() * 100;
            momentumList.add(new AbstractMap.SimpleEntry<>(entry.getKey(), momentum));
        }
        return momentumList.stream().sorted((a, b) -> Double.compare(b.getValue(), a.getValue())).limit(topN).map(Map.Entry::getKey).collect(Collectors.toList());
    }

    /**
     * 基于价值策略选股
     * 计算当前价格与20日均线的偏离度，选择偏离度最大的topN只股票
     *
     * @param stockMap 股票数据Map
     * @param topN     选择的股票数量
     * @return 选中的股票代码列表
     */
    public static List<String> selectByValue(Map<String, List<Stock>> stockMap, int topN) {
        List<Map.Entry<String, Double>> valueList = new ArrayList<>();
        for (Map.Entry<String, List<Stock>> entry : stockMap.entrySet()) {
            List<Stock> stocks = entry.getValue();
            if (stocks.size() < 20)
                continue;                        // 计算当前价格与20日均线的偏离度
            double ma20 = stocks.subList(stocks.size() - 20, stocks.size()).stream().mapToDouble(Stock::getClose).average().orElse(0);
            double currentPrice = stocks.get(stocks.size() - 1).getClose();
            double deviation = (ma20 - currentPrice) / ma20 * 100;
            valueList.add(new AbstractMap.SimpleEntry<>(entry.getKey(), deviation));
        }
        return valueList.stream().sorted((a, b) -> Double.compare(b.getValue(), a.getValue())).limit(topN).map(Map.Entry::getKey).collect(Collectors.toList());
    }

    /**
     * 基于成长策略选股
     * 计算股票的放量程度和涨幅，综合评分后选择得分最高的topN只股票
     *
     * @param stockMap 股票数据Map
     * @param topN     选择的股票数量
     * @return 选中的股票代码列表
     */
    public static List<String> selectByGrowth(Map<String, List<Stock>> stockMap, int topN) {
        List<Map.Entry<String, Double>> growthList = new ArrayList<>();
        for (Map.Entry<String, List<Stock>> entry : stockMap.entrySet()) {
            List<Stock> stocks = entry.getValue();
            if (stocks.size() < 2) continue;
            Stock today = stocks.get(stocks.size() - 1);
            Stock yesterday = stocks.get(stocks.size() - 2);
            // 计算放量程度和涨幅
            double volumeRatio = today.getVolume() / yesterday.getVolume();
            double priceChange = (today.getClose() - yesterday.getClose()) / yesterday.getClose() * 100;
            // 综合评分
            double score = volumeRatio * priceChange;
            growthList.add(new AbstractMap.SimpleEntry<>(entry.getKey(), score));
        }
        return growthList.stream().sorted((a, b) -> Double.compare(b.getValue(), a.getValue())).limit(topN).map(Map.Entry::getKey).collect(Collectors.toList());
    }

    /**
     * 多因子选股策略
     * 综合考虑动量因子、均线偏离因子和成交量因子，计算综合得分
     *
     * @param stockMap 股票数据Map
     * @param days     计算周期
     * @param topN     选择的股票数量
     * @return 选中的股票代码列表
     */
    public static List<String> selectMultiFactor(Map<String, List<Stock>> stockMap, int days, int topN) {
        class FactorScore {
            String code;
            double momentum;
            double maBias;
            double volRatio;
            double totalScore;

            FactorScore(String code, double momentum, double maBias, double volRatio) {
                this.code = code;
                this.momentum = momentum;
                this.maBias = maBias;
                this.volRatio = volRatio;
            }
        }
        List<FactorScore> scores = new ArrayList<>();
        for (Map.Entry<String, List<Stock>> entry : stockMap.entrySet()) {
            List<Stock> stocks = entry.getValue();
            if (stocks.size() < days) continue;
            Stock start = stocks.get(stocks.size() - days);
            Stock end = stocks.get(stocks.size() - 1);
            double momentum = (end.getClose() - start.getClose()) / start.getClose();
            // 均线因子
            double sum = 0;
            for (int i = stocks.size() - days; i < stocks.size(); i++) {
                sum += stocks.get(i).getClose();
            }
            double ma = sum / days;
            double maBias = (end.getClose() - ma) / ma;
            // 量能因子
            double volSum = 0;
            for (int i = stocks.size() - days; i < stocks.size(); i++) {
                volSum += stocks.get(i).getVolume();
            }
            double avgVol = volSum / days;
            double volRatio = avgVol == 0 ? 0 : end.getVolume() / avgVol;
            scores.add(new FactorScore(entry.getKey(), momentum, maBias, volRatio));
        }
        // 归一化
        double minM = Double.MAX_VALUE, maxM = -Double.MAX_VALUE;
        double minB = Double.MAX_VALUE, maxB = -Double.MAX_VALUE;
        double minV = Double.MAX_VALUE, maxV = -Double.MAX_VALUE;
        for (FactorScore s : scores) {
            minM = Math.min(minM, s.momentum);
            maxM = Math.max(maxM, s.momentum);
            minB = Math.min(minB, s.maBias);
            maxB = Math.max(maxB, s.maBias);
            minV = Math.min(minV, s.volRatio);
            maxV = Math.max(maxV, s.volRatio);
        }
        for (FactorScore s : scores) {
            double mScore = (maxM - minM) == 0 ? 0 : (s.momentum - minM) / (maxM - minM);
            double bScore = (maxB - minB) == 0 ? 0 : (s.maBias - minB) / (maxB - minB);
            double vScore = (maxV - minV) == 0 ? 0 : (s.volRatio - minV) / (maxV - minV);
            // 权重可调整
            s.totalScore = 0.5 * mScore + 0.3 * bScore + 0.2 * vScore;
        }
        scores.sort((a, b) -> Double.compare(b.totalScore, a.totalScore));
        List<String> selected = new ArrayList<>();
        for (int i = 0; i < Math.min(topN, scores.size()); i++) {
            selected.add(scores.get(i).code);
        }
        return selected;
    }

    /**
     * KDJ金叉选股策略
     * 寻找KDJ指标形成金叉的股票（K线从下方穿过D线）
     *
     * @param stockMap 股票数据Map
     * @param period   KDJ计算周期
     * @return 形成KDJ金叉的股票代码列表
     */
    public static List<String> selectKDJGoldenCross(Map<String, List<Stock>> stockMap, int period) {
        List<String> selected = new ArrayList<>();

        for (Map.Entry<String, List<Stock>> entry : stockMap.entrySet()) {
            List<Stock> stocks = entry.getValue();
            if (stocks.size() < period + 1) continue; // 需要至少period+1天的数据

            // 计算最近两天的KDJ值
            double[][] kdj1 = calculateKDJ(stocks.subList(stocks.size() - period - 1, stocks.size() - 1));
            double[][] kdj2 = calculateKDJ(stocks.subList(stocks.size() - period, stocks.size()));

            // 检查是否形成金叉
            if (isGoldenCross(kdj1[0][2], kdj1[1][2], kdj2[0][2], kdj2[1][2])) {
                selected.add(entry.getKey());
            }
        }

        return selected;
    }

    /**
     * 计算KDJ指标值
     *
     * @param stocks 股票历史数据列表
     * @return 返回一个二维数组，包含昨天和今天的K、D、J值
     */
    private static double[][] calculateKDJ(List<Stock> stocks) {
        int n = 9; // 默认参数
        double[][] result = new double[2][3]; // [昨天,今天][K,D,J]

        // 获取period内的最高价和最低价
        double highestHigh = Double.MIN_VALUE;
        double lowestLow = Double.MAX_VALUE;
        for (Stock stock : stocks.subList(stocks.size() - n, stocks.size())) {
            highestHigh = Math.max(highestHigh, stock.getHigh());
            lowestLow = Math.min(lowestLow, stock.getLow());
        }

        // 计算RSV
        Stock lastStock = stocks.get(stocks.size() - 1);
        Stock prevStock = stocks.get(stocks.size() - 2);

        double rsv1 = ((prevStock.getClose() - lowestLow) / (highestHigh - lowestLow)) * 100;
        double rsv2 = ((lastStock.getClose() - lowestLow) / (highestHigh - lowestLow)) * 100;

        // 计算昨天的KDJ
        result[0][0] = 50; // 初始K值
        result[0][1] = 50; // 初始D值
        result[0][0] = 2.0 / 3 * 50 + 1.0 / 3 * rsv1;
        result[0][1] = 2.0 / 3 * 50 + 1.0 / 3 * result[0][0];
        result[0][2] = 3 * result[0][0] - 2 * result[0][1];

        // 计算今天的KDJ
        result[1][0] = 2.0 / 3 * result[0][0] + 1.0 / 3 * rsv2;
        result[1][1] = 2.0 / 3 * result[0][1] + 1.0 / 3 * result[1][0];
        result[1][2] = 3 * result[1][0] - 2 * result[1][1];

        return result;
    }

    /**
     * 判断是否形成KDJ金叉
     *
     * @param k1 昨天的K值
     * @param d1 昨天的D值
     * @param k2 今天的K值
     * @param d2 今天的D值
     * @return 是否形成金叉
     */
    private static boolean isGoldenCross(double k1, double d1, double k2, double d2) {
        return k1 <= d1 && k2 > d2; // 昨天K线在D线下方，今天K线在D线上方
    }

    /**
     * 首次涨停选股策略
     * 选择最近出现首次涨停的股票（之前lookBackDays天内未出现过涨停）
     *
     * @param stockMap     股票数据Map
     * @param lookBackDays 回看的天数
     * @return 首次涨停的股票代码列表
     */
    public static List<String> selectFirstLimitUp(Map<String, List<Stock>> stockMap, int lookBackDays) {
        List<String> selected = new ArrayList<>();

        for (Map.Entry<String, List<Stock>> entry : stockMap.entrySet()) {
            List<Stock> stocks = entry.getValue();
            if (stocks.size() < lookBackDays + 1) continue; // 需要足够的历史数据

            // 获取最近几天的数据
            int lastIndex = stocks.size() - 1;
            Stock yesterday = stocks.get(lastIndex - 1);
            Stock beforeYesterday = stocks.get(lastIndex - 2);

            // 计算昨日涨幅
            double yesterdayReturn = (yesterday.getClose() - beforeYesterday.getClose()) / beforeYesterday.getClose();

            // 检查是否是涨停（涨幅>=9.8%，考虑到可能存在的价格波动）
            if (yesterdayReturn >= 0.098) {
                // 检查之前是否有涨停
                boolean hasPreLimitUp = false;
                for (int i = lastIndex - 3; i >= Math.max(0, lastIndex - lookBackDays); i--) {
                    Stock current = stocks.get(i + 1);
                    Stock previous = stocks.get(i);
                    double dayReturn = (current.getClose() - previous.getClose()) / previous.getClose();
                    if (dayReturn >= 0.098) {
                        hasPreLimitUp = true;
                        break;
                    }
                }

                // 如果之前没有涨停，则为首次涨停
                if (!hasPreLimitUp) {
                    selected.add(entry.getKey());
                }
            }
        }

        return selected;
    }

    /**
     * 一进二买点选股策略
     * 寻找完成第一波上涨后回调，即将开始第二波上涨的股票
     *
     * @param stockMap 股票数据Map
     * @param period   观察周期（默认20天）
     * @param topN     选择的股票数量
     * @return 符合一进二形态的股票代码列表
     */
    public static List<String> selectOneToTwo(Map<String, List<Stock>> stockMap, int period, int topN) {
        List<Map.Entry<String, Double>> scoreList = new ArrayList<>();

        for (Map.Entry<String, List<Stock>> entry : stockMap.entrySet()) {
            List<Stock> stocks = entry.getValue();
            if (stocks.size() < period) continue;

            // 获取观察周期内的数据
            List<Stock> periodStocks = stocks.subList(stocks.size() - period, stocks.size());

            // 计算第一波上涨的高点
            double firstPeakPrice = -1;
            int peakIndex = -1;
            for (int i = 5; i < periodStocks.size() - 5; i++) {
                Stock current = periodStocks.get(i);
                boolean isPeak = true;
                // 判断是否是高点（前后5天的最高价）
                for (int j = i - 5; j <= i + 5; j++) {
                    if (j == i) continue;
                    if (periodStocks.get(j).getHigh() > current.getHigh()) {
                        isPeak = false;
                        break;
                    }
                }
                if (isPeak) {
                    firstPeakPrice = current.getHigh();
                    peakIndex = i;
                    break;
                }
            }

            if (peakIndex == -1) continue; // 没有找到明显的高点

            // 计算回调幅度
            double lowestPrice = Double.MAX_VALUE;
            int lowestIndex = -1;
            for (int i = peakIndex + 1; i < periodStocks.size() - 3; i++) {
                if (periodStocks.get(i).getLow() < lowestPrice) {
                    lowestPrice = periodStocks.get(i).getLow();
                    lowestIndex = i;
                }
            }

            if (lowestIndex == -1) continue;

            // 计算回调幅度
            double pullbackRate = (firstPeakPrice - lowestPrice) / firstPeakPrice;

            // 计算企稳反弹的强度
            Stock latest = periodStocks.get(periodStocks.size() - 1);
            double reboundRate = (latest.getClose() - lowestPrice) / lowestPrice;

            // 计算最近3天的成交量相比前期的放大倍数
            double recentAvgVolume = periodStocks.subList(periodStocks.size() - 3, periodStocks.size())
                    .stream()
                    .mapToDouble(Stock::getVolume)
                    .average()
                    .orElse(0);

            double previousAvgVolume = periodStocks.subList(lowestIndex - 3, lowestIndex)
                    .stream()
                    .mapToDouble(Stock::getVolume)
                    .average()
                    .orElse(1);

            double volumeRatio = recentAvgVolume / previousAvgVolume;

            // 综合评分：回调幅度(30%) + 反弹强度(40%) + 成交量放大(30%)
            double score = 0.3 * (pullbackRate >= 0.2 && pullbackRate <= 0.4 ? 1 : 0) +
                    0.4 * (reboundRate >= 0.05 ? 1 : reboundRate / 0.05) +
                    0.3 * (volumeRatio >= 1.5 ? 1 : volumeRatio / 1.5);

            scoreList.add(new AbstractMap.SimpleEntry<>(entry.getKey(), score));
        }

        // 按得分排序并返回前topN只股票
        return scoreList.stream()
                .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
                .limit(topN)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }

    /**
     * 龙头股选股策略
     * 选择具有行业领先地位、成交量大、涨幅领先、资金持续流入的股票
     *
     * @param stockMap 股票数据Map
     * @param period   观察周期（默认20天）
     * @param topN     选择的股票数量
     * @return 龙头股票代码列表
     */
    public static List<String> selectLeaderStocks(Map<String, List<Stock>> stockMap, int period, int topN) {
        List<Map.Entry<String, Double>> scoreList = new ArrayList<>();

        for (Map.Entry<String, List<Stock>> entry : stockMap.entrySet()) {
            List<Stock> stocks = entry.getValue();
            if (stocks.size() < period) continue;

            // 获取观察周期内的数据
            List<Stock> periodStocks = stocks.subList(stocks.size() - period, stocks.size());

            // 1. 计算涨幅得分
            Stock startStock = periodStocks.get(0);
            Stock endStock = periodStocks.get(periodStocks.size() - 1);
            double priceGrowth = (endStock.getClose() - startStock.getClose()) / startStock.getClose();

            // 2. 计算成交量得分
            double avgVolume = periodStocks.stream()
                    .mapToDouble(Stock::getVolume)
                    .average()
                    .orElse(0);
            double recentVolume = periodStocks.subList(periodStocks.size() - 5, periodStocks.size())
                    .stream()
                    .mapToDouble(Stock::getVolume)
                    .average()
                    .orElse(0);
            double volumeScore = recentVolume / avgVolume;

            // 3. 计算强度得分
            int upDays = 0;
            double totalGain = 0;
            double totalLoss = 0;

            for (int i = 1; i < periodStocks.size(); i++) {
                double change = periodStocks.get(i).getClose() - periodStocks.get(i - 1).getClose();
                if (change > 0) {
                    upDays++;
                    totalGain += change;
                } else {
                    totalLoss -= change;
                }
            }

            double strengthScore = (totalLoss == 0) ? 1 : totalGain / totalLoss;

            // 4. 计算趋势得分
            double ma5 = periodStocks.subList(periodStocks.size() - 5, periodStocks.size())
                    .stream()
                    .mapToDouble(Stock::getClose)
                    .average()
                    .orElse(0);
            double ma10 = periodStocks.subList(periodStocks.size() - 10, periodStocks.size())
                    .stream()
                    .mapToDouble(Stock::getClose)
                    .average()
                    .orElse(0);
            double ma20 = periodStocks.stream()
                    .mapToDouble(Stock::getClose)
                    .average()
                    .orElse(0);

            double trendScore = (ma5 > ma10 && ma10 > ma20) ? 1.0 : 0.0;

            // 5. 计算资金流入得分
            double moneyFlowScore = 0;
            for (int i = 1; i < periodStocks.size(); i++) {
                Stock curr = periodStocks.get(i);
                Stock prev = periodStocks.get(i - 1);
                if (curr.getClose() > prev.getClose() && curr.getVolume() > prev.getVolume()) {
                    moneyFlowScore++;
                }
            }
            moneyFlowScore /= (periodStocks.size() - 1);

            // 综合评分：涨幅(20%) + 成交量(20%) + 强度(20%) + 趋势(20%) + 资金流入(20%)
            double totalScore = 0.2 * (priceGrowth * 100) +
                    0.2 * (volumeScore) +
                    0.2 * (strengthScore) +
                    0.2 * (trendScore) +
                    0.2 * (moneyFlowScore);

            scoreList.add(new AbstractMap.SimpleEntry<>(entry.getKey(), totalScore));
        }

        // 按得分排序并返回前topN只股票
        return scoreList.stream()
                .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
                .limit(topN)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }
}